package com.jneagle.xlstool.bzwgjmx.view.gui;

import com.dwarfeng.dutil.basic.cna.model.ReferenceModel;
import com.dwarfeng.dutil.basic.cna.model.obs.ReferenceAdapter;
import com.dwarfeng.dutil.basic.gui.swing.MappingTableModel;
import com.dwarfeng.dutil.basic.gui.swing.SwingUtil;
import com.dwarfeng.subgrade.stack.exception.ServiceException;
import com.jneagle.xlstool.bzwgjmx.sdk.util.ServiceExceptionCodes;
import com.jneagle.xlstool.bzwgjmx.stack.bean.entity.StructuredData;
import com.jneagle.xlstool.bzwgjmx.stack.bean.entity.StructuredErrorInfo;
import com.jneagle.xlstool.bzwgjmx.stack.service.StructuredDataLoadService;
import com.jneagle.xlstool.bzwgjmx.stack.service.StructuredDataMaintainService;
import com.jneagle.xlstool.bzwgjmx.stack.service.StructuredErrorInfoMaintainService;
import com.jneagle.xlstool.bzwgjmx.stack.structure.ProgressObserver;
import com.jneagle.xlstool.bzwgjmx.stack.structure.ProgressStatus;
import com.jneagle.xlstool.bzwgjmx.view.handler.I18nHandler;
import com.jneagle.xlstool.bzwgjmx.view.handler.MessageHandler;
import com.jneagle.xlstool.bzwgjmx.view.handler.ModalHandler;
import com.jneagle.xlstool.bzwgjmx.view.structure.ModalItem;
import com.jneagle.xlstool.bzwgjmx.view.structure.OperationStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

/**
 * 结构化数据导入面板。
 *
 * @author DwArFeng
 * @since 1.0.0
 */
@Component
@DependsOn("viewConfiguration")
public class StructuredDataImportPanel extends JPanel {

    private static final long serialVersionUID = -1038340633380694696L;
    private static final Logger LOGGER = LoggerFactory.getLogger(StructuredDataImportPanel.class);

    private final ApplicationContext ctx;

    private final StructuredDataLoadService structuredDataLoadService;
    private final StructuredDataMaintainService structuredDataMaintainService;
    private final StructuredErrorInfoMaintainService structuredErrorInfoMaintainService;

    private final I18nHandler i18nHandler;
    private final Locale uiLocale;
    private final ModalHandler modalHandler;
    private final ThreadPoolTaskExecutor executor;
    private final MessageHandler messageHandler;

    private final ReferenceModel<File> structuredFileModel;
    private final ReferenceModel<String> structuredPasswordModel;
    private final ReferenceModel<String> notificationModel;
    private final ReferenceModel<ProgressStatus> progressStatusModel;
    private final MappingTableModel<StructuredData> structuredDataTableModel;
    private final MappingTableModel<StructuredErrorInfo> structuredErrorInfoTableModel;
    private final ReferenceModel<OperationStep> operationStepModel;
    private final ReferenceModel<File> exportFileModel;

    private final StructuredDataErrorDialog structuredDataErrorDialog;

    private final JTextField loadFileTextField = new JTextField();
    private final JButton selectFileButton = new JButton();
    private final JPasswordField passwordField = new JPasswordField();
    private final JButton loadFileButton = new JButton();
    private final JTextField resultTextField = new JTextField();
    private final JButton showErrorButton = new JButton();
    private final JTable structuredDataTable = new JTable();

    private final LoadFileObserver loadFileObserver = new LoadFileObserver();
    private final PasswordObserver passwordObserver = new PasswordObserver();
    private final StructuredDataLoadObserver structuredDataLoadObserver = new StructuredDataLoadObserver();
    private final StructuredDataTableModelListener structuredDataTableModelListener
            = new StructuredDataTableModelListener();
    private final StructuredErrorInfoTableModelListener structuredErrorInfoTableModelListener
            = new StructuredErrorInfoTableModelListener();

    public StructuredDataImportPanel(
            ApplicationContext ctx,
            StructuredDataLoadService structuredDataLoadService,
            StructuredDataMaintainService structuredDataMaintainService,
            StructuredErrorInfoMaintainService structuredErrorInfoMaintainService,
            I18nHandler i18nHandler,
            @Qualifier("uiLocale") Locale uiLocale,
            ModalHandler modalHandler,
            ThreadPoolTaskExecutor executor,
            MessageHandler messageHandler,
            @Qualifier("structuredFileModel") ReferenceModel<File> structuredFileModel,
            @Qualifier("structuredPasswordModel") ReferenceModel<String> structuredPasswordModel,
            @Qualifier("notificationModel") ReferenceModel<String> notificationModel,
            @Qualifier("progressStatusModel") ReferenceModel<ProgressStatus> progressStatusModel,
            @Qualifier("structuredDataTableModel") MappingTableModel<StructuredData> structuredDataTableModel,
            @Qualifier("structuredErrorInfoTableModel") MappingTableModel<StructuredErrorInfo> structuredErrorInfoTableModel,
            @Qualifier("operationStepModel") ReferenceModel<OperationStep> operationStepModel,
            @Qualifier("exportFileModel") ReferenceModel<File> exportFileModel,
            StructuredDataErrorDialog structuredDataErrorDialog
    ) {
        this.ctx = ctx;
        this.structuredDataMaintainService = structuredDataMaintainService;
        this.structuredErrorInfoMaintainService = structuredErrorInfoMaintainService;
        this.i18nHandler = i18nHandler;
        this.uiLocale = uiLocale;
        this.modalHandler = modalHandler;
        this.executor = executor;
        this.messageHandler = messageHandler;
        this.structuredDataLoadService = structuredDataLoadService;
        this.structuredFileModel = structuredFileModel;
        this.structuredPasswordModel = structuredPasswordModel;
        this.notificationModel = notificationModel;
        this.progressStatusModel = progressStatusModel;
        this.structuredDataTableModel = structuredDataTableModel;
        this.structuredErrorInfoTableModel = structuredErrorInfoTableModel;
        this.operationStepModel = operationStepModel;
        this.exportFileModel = exportFileModel;
        this.structuredDataErrorDialog = structuredDataErrorDialog;
    }

    @SuppressWarnings("DuplicatedCode")
    @PostConstruct
    public void postConstruct() {
        // 初始化界面。
        GridBagLayout gridBagLayout = new GridBagLayout();
        gridBagLayout.columnWidths = new int[]{0, 0, 0};
        gridBagLayout.rowHeights = new int[]{0, 0, 0, 0};
        gridBagLayout.columnWeights = new double[]{0.0, 1.0, 0.0};
        gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 1.0};
        setLayout(gridBagLayout);

        JLabel loadFileLabel = new JLabel(i18nHandler.getMessage("ui.label.004"));
        GridBagConstraints gbcLoadFileLabel = new GridBagConstraints();
        gbcLoadFileLabel.insets = new Insets(0, 0, 0, 5);
        gbcLoadFileLabel.anchor = GridBagConstraints.EAST;
        gbcLoadFileLabel.gridx = 0;
        gbcLoadFileLabel.gridy = 0;
        add(loadFileLabel, gbcLoadFileLabel);

        loadFileTextField.setEditable(false);
        GridBagConstraints gbcLoadFileTextField = new GridBagConstraints();
        gbcLoadFileTextField.insets = new Insets(0, 0, 0, 5);
        gbcLoadFileTextField.fill = GridBagConstraints.BOTH;
        gbcLoadFileTextField.gridx = 1;
        gbcLoadFileTextField.gridy = 0;
        add(loadFileTextField, gbcLoadFileTextField);

        selectFileButton.setAction(new SelectFileAction());
        selectFileButton.setText(i18nHandler.getMessage("ui.label.005"));
        GridBagConstraints gbcSelectFileButton = new GridBagConstraints();
        gbcSelectFileButton.insets = new Insets(0, 0, 0, 0);
        gbcSelectFileButton.fill = GridBagConstraints.BOTH;
        gbcSelectFileButton.gridx = 2;
        gbcSelectFileButton.gridy = 0;
        add(selectFileButton, gbcSelectFileButton);

        JLabel passwordLabel = new JLabel(i18nHandler.getMessage("ui.label.007"));
        GridBagConstraints gbcPasswordLabel = new GridBagConstraints();
        gbcPasswordLabel.insets = new Insets(0, 0, 0, 5);
        gbcPasswordLabel.anchor = GridBagConstraints.EAST;
        gbcPasswordLabel.gridx = 0;
        gbcPasswordLabel.gridy = 1;
        add(passwordLabel, gbcPasswordLabel);

        passwordField.getDocument().addDocumentListener(new PasswordDocumentListener());
        GridBagConstraints gbcPasswordField = new GridBagConstraints();
        gbcPasswordField.insets = new Insets(0, 0, 0, 5);
        gbcPasswordField.fill = GridBagConstraints.BOTH;
        gbcPasswordField.gridx = 1;
        gbcPasswordField.gridy = 1;
        add(passwordField, gbcPasswordField);

        loadFileButton.setAction(new LoadFileAction());
        loadFileButton.setText(i18nHandler.getMessage("ui.label.008"));
        GridBagConstraints gbcLoadFileButton = new GridBagConstraints();
        gbcLoadFileButton.insets = new Insets(0, 0, 0, 0);
        gbcLoadFileButton.fill = GridBagConstraints.BOTH;
        gbcLoadFileButton.gridx = 2;
        gbcLoadFileButton.gridy = 1;
        add(loadFileButton, gbcLoadFileButton);

        JLabel resultLabel = new JLabel(i18nHandler.getMessage("ui.label.018"));
        GridBagConstraints gbcResultLabel = new GridBagConstraints();
        gbcResultLabel.insets = new Insets(0, 0, 0, 5);
        gbcResultLabel.anchor = GridBagConstraints.EAST;
        gbcResultLabel.gridx = 0;
        gbcResultLabel.gridy = 2;
        add(resultLabel, gbcResultLabel);

        resultTextField.setEditable(false);
        GridBagConstraints gbcResultTextField = new GridBagConstraints();
        gbcResultTextField.insets = new Insets(0, 0, 0, 5);
        gbcResultTextField.fill = GridBagConstraints.BOTH;
        gbcResultTextField.gridx = 1;
        gbcResultTextField.gridy = 2;
        add(resultTextField, gbcResultTextField);

        showErrorButton.setAction(new ShowErrorDialogAction());
        showErrorButton.setText(i18nHandler.getMessage("ui.label.022"));
        GridBagConstraints gbcShowErrorButton = new GridBagConstraints();
        gbcShowErrorButton.insets = new Insets(0, 0, 0, 0);
        gbcShowErrorButton.fill = GridBagConstraints.BOTH;
        gbcShowErrorButton.gridx = 2;
        gbcShowErrorButton.gridy = 2;
        add(showErrorButton, gbcShowErrorButton);

        I18nTableCellRenderer structuredDataHeaderRender = ctx.getBean(
                I18nTableCellRenderer.class,
                ctx.getBean(I18nHandler.class),
                structuredDataTable.getTableHeader().getDefaultRenderer()
        );
        structuredDataTable.getTableHeader().setDefaultRenderer(structuredDataHeaderRender);
        structuredDataTable.setModel(structuredDataTableModel);
        JScrollPane structuredDataScrollPane = new JScrollPane();
        structuredDataScrollPane.setViewportView(structuredDataTable);
        GridBagConstraints gbcStructuredDataTable = new GridBagConstraints();
        gbcStructuredDataTable.insets = new Insets(5, 0, 0, 0);
        gbcStructuredDataTable.fill = GridBagConstraints.BOTH;
        gbcStructuredDataTable.gridx = 0;
        gbcStructuredDataTable.gridy = 3;
        gbcStructuredDataTable.gridwidth = 3;
        add(structuredDataScrollPane, gbcStructuredDataTable);

        // 添加侦听器。
        structuredFileModel.addObserver(loadFileObserver);
        structuredPasswordModel.addObserver(passwordObserver);
        structuredDataTableModel.addTableModelListener(structuredDataTableModelListener);
        structuredErrorInfoTableModel.addTableModelListener(structuredErrorInfoTableModelListener);

        // 同步属性。
        syncProperties();
    }

    @SuppressWarnings("DuplicatedCode")
    private void syncProperties() {
        SwingUtil.invokeInEventQueue(() -> {
            File file = structuredFileModel.get();
            loadFileTextField.setText(
                    Optional.ofNullable(file).map(File::getAbsolutePath).orElse("")
            );
            if (Objects.isNull(file)) {
                loadFileButton.setEnabled(false);
            } else {
                loadFileButton.setEnabled(file.exists());
            }
            resultTextField.setText(i18nHandler.getMessage(
                    "ui.label.019",
                    new Object[]{structuredDataTableModel.size(), structuredErrorInfoTableModel.size()}
            ));
            showErrorButton.setEnabled(!structuredErrorInfoTableModel.isEmpty());
        });
    }

    @PreDestroy
    public void preDestroy() {
        // 移除侦听器。
        structuredFileModel.removeObserver(loadFileObserver);
        structuredPasswordModel.removeObserver(passwordObserver);
        structuredDataTableModel.removeTableModelListener(structuredDataTableModelListener);
        structuredErrorInfoTableModel.removeTableModelListener(structuredErrorInfoTableModelListener);
    }

    public void loadData() {
        structuredDataTableModel.clear();
        structuredErrorInfoTableModel.clear();
        executor.submit(() -> {
            try {
                List<StructuredData> structuredDataList = structuredDataMaintainService.lookupAsList();
                List<StructuredErrorInfo> structuredErrorInfoList =
                        structuredErrorInfoMaintainService.lookupAsList();
                structuredDataTableModel.addAll(structuredDataList);
                structuredErrorInfoTableModel.addAll(structuredErrorInfoList);
            } catch (Exception e) {
                messageHandler.error(this, "ui.label.031");
            }
        });
    }

    private class LoadFileObserver extends ReferenceAdapter<File> {

        @Override
        public void fireSet(File oldValue, File newValue) {
            SwingUtil.invokeInEventQueue(() -> {
                loadFileTextField.setText(
                        Optional.ofNullable(newValue).map(File::getAbsolutePath).orElse("")
                );
                if (Objects.isNull(newValue)) {
                    loadFileButton.setEnabled(false);
                } else {
                    loadFileButton.setEnabled(newValue.exists());
                }
            });
        }
    }

    private class SelectFileAction extends AbstractAction {

        private static final long serialVersionUID = 1285978181579200675L;

        @SuppressWarnings("DuplicatedCode")
        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                // 创建文件选择器并进行设置。
                JFileChooser jfc = new JFileChooser();
                jfc.setLocale(uiLocale);
                jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
                jfc.setFileFilter(new FileNameExtensionFilter(i18nHandler.getMessage("ui.label.006"), "xls"));
                jfc.setAcceptAllFileFilterUsed(false);
                // 如果已经有文件了，将当前路径设置为指定文件的路径。
                File currentFile = structuredFileModel.get();
                if (Objects.nonNull(currentFile)) {
                    if (currentFile.isDirectory()) {
                        jfc.setCurrentDirectory(currentFile);
                    } else {
                        jfc.setCurrentDirectory(currentFile.getParentFile());
                    }
                }
                // 显示界面，让用户选择文件。
                int result = jfc.showOpenDialog(StructuredDataImportPanel.this);
                // 如果用户按下了确定键，则将文件模型设置为用户选择的文件。
                if (Objects.equals(result, JFileChooser.APPROVE_OPTION)) {
                    passwordField.setText("");
                    File file = jfc.getSelectedFile();
                    structuredFileModel.set(file);
                    exportFileModel.set(file);
                }
            } catch (Exception ex) {
                LOGGER.warn("选择文件时发生异常，异常信息如下: ", ex);
                messageHandler.error(StructuredDataImportPanel.this, "ui.label.009");
            }
        }
    }

    private class PasswordObserver extends ReferenceAdapter<String> {

        private boolean enabled = true;

        @Override
        public void fireSet(String oldValue, String newValue) {
            if (!enabled) {
                return;
            }
            SwingUtil.invokeInEventQueue(() -> passwordField.setText(newValue));
        }

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }

    private class PasswordDocumentListener implements DocumentListener {

        @Override
        public void insertUpdate(DocumentEvent e) {
            changePassword();
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            changePassword();
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            // Do nothing
        }

        private void changePassword() {
            passwordObserver.setEnabled(false);
            structuredPasswordModel.set(new String(passwordField.getPassword()));
            passwordObserver.setEnabled(true);
        }
    }

    private class LoadFileAction extends AbstractAction implements Runnable {

        private static final long serialVersionUID = -3459453100819555110L;

        @Override
        public void actionPerformed(ActionEvent e) {
            // 在后台线程中执行方法，避免前端界面卡顿。
            executor.execute(this);
        }

        @Override
        public void run() {
            try {
                // 通过控制按钮的 enabled 属性，此处可以保证按钮按下时文件是有效的。
                File file = structuredFileModel.get();
                String password = structuredPasswordModel.get();

                // 保存文件读取的模态信息。
                modalHandler.setParsedValue(ModalItem.MODAL_STRUCTURED_DATA_LAST_LOAD_FILE, file);
                modalHandler.setParsedValue(ModalItem.MODAL_STRUCTURED_DATA_LAST_LOAD_FLAG, true);

                // 更新通知面板标签文本。
                notificationModel.set(i18nHandler.getMessage("ui.label.012"));

                // 创建侦听
                structuredDataLoadService.addObserver(structuredDataLoadObserver);
                // 调用接口读取文件，并捕获异常。
                try {
                    structuredDataLoadService.load(file, password);
                } catch (ServiceException ex) {
                    // 分析异常。
                    int code = ex.getCode().getCode();
                    if (Objects.equals(code, ServiceExceptionCodes.WRONG_PASSWORD.getCode())) {
                        messageHandler.warn(StructuredDataImportPanel.this, "ui.label.066");
                    } else {
                        messageHandler.warn(StructuredDataImportPanel.this, "ui.label.015");
                    }
                    // 更新通知面板标签文本。
                    notificationModel.set(i18nHandler.getMessage("ui.label.013"));
                    return;
                } finally {
                    // 移除侦听。
                    structuredDataLoadService.removeObserver(structuredDataLoadObserver);
                }

                // 重新加载结构化数据。
                loadData();

                // 判断是否存在数据错误，如果有错误，则需要自动弹出错误对话框。
                if (structuredErrorInfoTableModel.isEmpty()) {
                    notificationModel.set(i18nHandler.getMessage("ui.label.020"));
                } else {
                    notificationModel.set(i18nHandler.getMessage("ui.label.021"));
                }

                // 更新前端界面的操作步骤。
                operationStepModel.set(OperationStep.RAW_DATA_IMPORT);
            } catch (Exception ex) {
                LOGGER.warn("加载文件时发生异常，异常信息如下: ", ex);
                messageHandler.error(StructuredDataImportPanel.this, "ui.label.011");
                operationStepModel.set(OperationStep.STRUCTURED_DATA_IMPORT);
            }
        }
    }

    private class StructuredDataLoadObserver implements ProgressObserver {

        @Override
        public void onProgressChanged(ProgressStatus progressStatus) {
            progressStatusModel.set(progressStatus);
        }
    }

    private class StructuredDataTableModelListener implements TableModelListener {

        @Override
        public void tableChanged(TableModelEvent e) {
            SwingUtil.invokeInEventQueue(() -> resultTextField.setText(i18nHandler.getMessage(
                    "ui.label.019",
                    new Object[]{structuredDataTableModel.size(), structuredErrorInfoTableModel.size()}
            )));
        }
    }

    private class StructuredErrorInfoTableModelListener implements TableModelListener {

        @Override
        public void tableChanged(TableModelEvent e) {
            SwingUtil.invokeInEventQueue(() -> {
                resultTextField.setText(i18nHandler.getMessage(
                        "ui.label.019",
                        new Object[]{structuredDataTableModel.size(), structuredErrorInfoTableModel.size()}
                ));
                showErrorButton.setEnabled(!structuredErrorInfoTableModel.isEmpty());
            });
        }
    }

    private class ShowErrorDialogAction extends AbstractAction {

        private static final long serialVersionUID = 9204639504725877140L;

        @Override
        public void actionPerformed(ActionEvent e) {
            structuredDataErrorDialog.setLocationRelativeTo(ctx.getBean(MainFrame.class));
            structuredDataErrorDialog.setVisible(true);
        }
    }
}
